import json
import os
import sys
import subprocess

from typing import List
from dataclasses import dataclass 

import dbus
import dbus._dbus
import fire
import xml
import xml.etree.ElementTree as et

loginuser = os.getlogin()


COLUMS = ['NAME', 'PID', 'PROCESS', 'USER', 'CONNECTION', 'UNIT', 'SESSION', 'DESCRIPTION']

# {
#         "name" : ":1.0",
#         "pid" : 3939,
#         "process" : "avahi-daemon",
#         "user" : "avahi",
#         "connection" : ":1.0",
#         "unit" : "avahi-daemon.service",
#         "session" : null,
#         "description" : null
# },


@dataclass
class BusctlPretty:
    name: str
    pid: str
    process: str
    user: str
    connection: str
    unit: str
    session: str
    description: str



TYPE_SESSION = dbus._dbus.BusConnection.TYPE_SESSION
TYPE_SYSTEM = dbus._dbus.BusConnection.TYPE_SYSTEM

from wrappers.org_freedesktop_dbus import org_freedesktop_DBusWrapper

class FreedesktopDBusWrapper():

    '''
    def list_services():
        # 连接到系统总线
        bus = dbus.SystemBus()
        # 获取DBus对象
        proxy = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
        # 获取DBus接口
        dbus_interface = dbus.Interface(proxy, 'org.freedesktop.DBus')
        # 调用ListNames方法获取所有服务名称
        services = dbus_interface.ListNames()
        return services
    '''

    # TYPE_SESSION
    # TYPE_SYSTEM
    def __init__(self, bustype):
        self.wrapper = org_freedesktop_DBusWrapper(bustype)

    def ListNames(self) -> List[str]:
        return [name for name in self.wrapper.ListNames() if not name.startswith(':')]
        
class Busctl:

    def __init__(self):
        self.command = ''
        # self.busctl = subprocess.Popen(["busctl"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # self.busctl.stdin.write(b"--user --machine=org.freedesktop.machine1 list\n")
        # self.busctl.stdin.flush()
        # self.busctl = subprocess.Popen(["busctl", "--json=pretty"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def __run__(self, *args):
        commands = ["busctl"]
        commands.extend(args)
        self.busctl = subprocess.Popen(commands, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        return self

    def __read__(self):
        return self.busctl.stdout.read().decode()

    def read_pretty(self, *args):
        return self.__run__('--json=pretty', *args).__read__()

    def pretty(self) -> List[BusctlPretty]:
        items = []
        for item in json.loads(self.read_pretty()):
            items.append(BusctlPretty(**item))
        return items

    def __list__(self, *args):
        return self.__run__('list', *args).__read__()
    
    def getlogin(self):
        return os.getlogin()
    
    # def names_of_busctl(self):
    
    def names(self):
        return [i.name for i in self.pretty() if not i.name.startswith(':')]
    
    def names_for_user(self):
        return [i.name for i in self.pretty() if not i.name.startswith(':') and i.user == loginuser]

    def names_for_root(self):
        return [i.name for i in self.pretty() if not i.name.startswith(':') and i.user == 'root']

    def objects_for_tree_root(self, name:str):
        return self.__run__('tree', name, '--list').__read__().splitlines()

    def objects_for_tree_user(self, name:str):
        return self.__run__('tree', '--user', name, '--list').__read__().splitlines()
    def __introspect__(self, *args):
        return self.__run__('introspect', *args)
    
    def xml_introspect(self, type:str, name:str, object:str):
        return self.__introspect__('--xml-interface', f'--{type}', name, object).__read__()

    def xml_introspect_root(self, name:str, object:str):
        return self.__introspect__('--xml-interface', name, object).__read__()
    def xml_introspect_user(self, name:str, object:str):
        return self.__introspect__('--xml-interface', '--user', name, object).__read__()
    

    def introspect_interfaces(self, type:str, name:str, object:str):
        output = self.xml_introspect(type, name, object)
        xmls = et.fromstring(output)
        names = []
        for xml in xmls:
            if xml.tag == 'interface':
                names.append(xml.get('name'))
        return names

    def introspect_interfaces_root(self,name:str, object:str):
        output = self.xml_introspect_root(name, object)
        xmls = et.fromstring(output)
        names = []
        for xml in xmls:
            if xml.tag == 'interface':
                names.append(xml.get('name'))
        return names
    def introspect_interfaces_user(self, name:str, object:str):
        output = self.xml_introspect_user(name, object)
        root = et.fromstring(output)
        names = []
        for child in root:
            if child.tag == 'interface':
                names.append(child.get('name'))
        return names

    # def introspect(self, name:str, object:str, interface:str):
    #     output = self.xml_introspect(name, object)
    #     data = et.fromstring(output)
    #     return data.findall('.//interface')

    # def introspect_user(self, name:str, object:str):
    #     output = self.xml_introspect_user(name, object)
    #     data = et.fromstring(output)
    #     return data.findall('.//interface') 

# from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QPushButton, QStatusBar, QTextEdit)
# from PySide6.QtGui import (QClipboard)
# from PySide6.QtCore import (Qt, Signal, Slot, QFile, QIODeviceBase, QThread, QObject, QTimer)
from PySide6 import QtCore, QtGui, QtWidgets

from ui_mainwindow import Ui_MainWindow

class BusctlWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.splitter.setSizes([1,4])
        _font = self.ui.text_object.font()
        _font.setFamily('Courier New')
        self.ui.text_object.setFont(_font)
        self.ui.text_interface.setFont(_font)
        self.ui.text_interface_introspect.setFont(_font)
        self.ui.text_interface_wrapper.setFont(_font)
        
        self.on_radio_user_clicked()
        self.ui.edit_search
        # QtCore.QTimer.singleShot(1000, self.close)

    @QtCore.Slot()
    def on_radio_system_clicked(self):
        self.ui.list_names.clear()
        # self.ui.list_names.addItems(Busctl().names())
        text = self.ui.edit_search.text()
        self.ui.list_names.addItems([name for name in FreedesktopDBusWrapper(TYPE_SYSTEM).ListNames() if text == "" or name.lower().find(text.lower())>=0])
    @QtCore.Slot()
    def on_radio_user_clicked(self):
        self.ui.list_names.clear()
        # self.ui.list_names.addItems(Busctl().names_for_user())
        text = self.ui.edit_search.text()
        self.ui.list_names.addItems([name for name in FreedesktopDBusWrapper(TYPE_SESSION).ListNames() if text == "" or name.lower().find(text.lower())>=0])

    @QtCore.Slot(str)
    def on_edit_search_textChanged(self, text):
        if self.ui.radio_system.isChecked():
            self.on_radio_system_clicked()
        else:
            self.on_radio_user_clicked()
    @QtCore.Slot(QtWidgets.QListWidgetItem,QtWidgets.QListWidgetItem)
    def on_list_names_currentItemChanged(self, current:QtWidgets.QListWidgetItem, previous:QtWidgets.QListWidgetItem):
        self.ui.list_objects.clear()
        if not current:
            return
        
        text = current.text()
        if self.ui.radio_system.isChecked():
            objects = Busctl().objects_for_tree_root(text)
        else:
            objects = Busctl().objects_for_tree_user(text)
        self.ui.list_objects.addItems(objects)

    @QtCore.Slot(QtWidgets.QListWidgetItem,QtWidgets.QListWidgetItem)
    def on_list_objects_currentItemChanged(self, current:QtWidgets.QListWidgetItem, previous:QtWidgets.QListWidgetItem):
        self.ui.text_object.clear()
        if not current:
            return
        
        # busctl introspect --user com.deepin.dde.Dock /com/deepin/dde/Dock
        type = self.ui.radio_system.isChecked() and 'system' or 'user'
        name = self.ui.list_names.currentItem().text()
        object = self.ui.list_objects.currentItem().text()
        self.ui.text_object.setPlainText(f'# busctl introspect {type} {name} {object} #')
        self.ui.text_object.append(Busctl().__introspect__(f'--{type}', name, object).__read__())
        self.ui.text_object.append("")

        self.ui.list_interfaces.clear()
        self.ui.list_interfaces.addItems(Busctl().introspect_interfaces(type, name, object))

    @QtCore.Slot(QtWidgets.QListWidgetItem, QtWidgets.QListWidgetItem)
    def on_list_interfaces_currentItemChanged(self, current:QtWidgets.QListWidgetItem, previous:QtWidgets.QListWidgetItem):
        self.ui.text_interface.clear()
        if not self.ui.list_objects.currentItem():
            return
        
        type = self.ui.radio_system.isChecked() and 'system' or 'user'
        type = self.ui.radio_system.isChecked() and 'system' or 'user'
        name = self.ui.list_names.currentItem().text()
        object = self.ui.list_objects.currentItem().text()

        if not current:
            self.ui.text_interface.setPlainText(f'# busctl introspect {type} {name} {object} {name} --xml-interface #')
            self.ui.text_interface.setPlainText(Busctl().xml_introspect(type, name, object))
            return

        interface = self.ui.list_interfaces.currentItem().text()
        self.ui.text_interface.setPlainText(f'# busctl introspect {type} {name} {object} {name} {interface} --xml-interface #')
        node = et.fromstring(Busctl().xml_introspect(type, name, object))
        # self.ui.text_interface.setPlainText(node.tag)
        # introspect 结构
        for child in node:
            self.ui.text_interface.append(f'{child.tag} {child.attrib}')
            if child.tag == 'interface':
                for subchild in child:
                    self.ui.text_interface.append(f' -- {subchild.tag} {subchild.attrib}')

        # xml 节点
        for child in node:
            self.ui.text_interface.append(f'{child.tag} {child.attrib}')
            if child.tag == 'interface':
                if child.attrib['name'] == interface:
                    self.ui.text_interface_introspect.setPlainText(et.tostring(child, encoding='unicode'))

        # wrapper
        for child in node:
            if child.tag == 'interface':
                if child.attrib['name'] == interface:
                    bustype = self.ui.radio_system.isChecked() and TYPE_SYSTEM or TYPE_SESSION
                    wrappercode = self.generate_wrapper_interface_code(bustype, name, object, child)
                    self.ui.text_interface_wrapper.setPlainText(wrappercode)

    def generate_wrapper_interface_code(self, bustype, name, object, node:et.Element):
        interface = node.attrib['name']
        wrapper_interface_name = interface.replace('.', '_')

        wrapper_methods_code = []
        for child in node:
            if child.tag == 'method':
                wrapper_method = child
                wrapper_method_name = wrapper_method.attrib['name']
                wrapper_method_args = []

                for method_child in wrapper_method:
                    if method_child.tag == 'arg':
                        wrapper_method_arg = method_child
                        # wrapper_method_arg_name = wrapper_method_arg.attrib['name']
                        if wrapper_method_arg.attrib['direction'] == 'in':
                            if 'name' in wrapper_method_arg.attrib:
                                wrapper_method_args.append(wrapper_method_arg.attrib['name'])
                            else:
                                wrapper_method_args.append(f'arg{len(wrapper_method_args)}')
                            # wrapper_method_args.append(wrapper_method_arg_name)

                method_code = self.generate_wrapper_method(wrapper_method_name, wrapper_method_args)
                wrapper_methods_code.append(method_code)


        return f'''
import dbus 
import dbus._dbus

class {wrapper_interface_name}Wrapper:
    def __init__(self, bustype):
        if bustype == dbus._dbus.BusConnection.TYPE_SESSION:
            self.bus = dbus.SessionBus()
        else:
            self.bus = dbus.SystemBus()
        self.object = self.bus.get_object('{name}', '{object}')
        self.interface = dbus.Interface(self.object, '{interface}')                        
''' + '\n'.join(wrapper_methods_code)



    def generate_wrapper_method(self, method_name, method_args):
        method_args = ', '.join(method_args)
        sub_method_args = method_args
        if method_args == "":
            method_args = f'self'
        else:
            method_args = f'self, {method_args}'
        return f'''\
    def {method_name}({method_args}):
        return self.interface.{method_name}({sub_method_args})
'''
    

    # @QtCore.Slot()
    # def on_list_interfaces_clicked(self):
        


if __name__ == '__main__':
    if len(sys.argv) > 1:
        fire.Fire(FreedesktopDBusWrapper(TYPE_SESSION))
    else:
        # print('Usage: busctl.py <command>')
        app = QtWidgets.QApplication(sys.argv)
        widget = BusctlWindow()
        widget.setWindowTitle("Busctl & FreeDesktopDBus")
        widget.show()
        screen = QtGui.QGuiApplication.primaryScreen()
        widget.move(screen.availableGeometry().center() - widget.rect().center())
        sys.exit(app.exec())

    # busctl = Busctl()


